//
//  OutputLanguageWriterSwift.m
//

#import "OutputLanguageWriterSwift.h"
#import "ClassBaseObject.h"
#import "ClassPropertiesObject.h"

@interface OutputLanguageWriterSwift ()

- (NSString *)swiftFileForClassObject:(ClassBaseObject *)classObject;
- (NSString *)swiftTypeForProperty:(ClassPropertiesObject *)property;

@end

@implementation OutputLanguageWriterSwift

- (BOOL)writeClassObjects:(NSDictionary *)classObjectsDict toURL:(NSURL *)url options:(NSDictionary *)options generatedError:(BOOL *)generatedErrorFlag {
    BOOL filesHaveHadError = NO;
    BOOL filesHaveBeenWritten = NO;

    NSArray *files = classObjectsDict.allValues;

    for (ClassBaseObject *base in files) {
        NSError *fileError = nil;
        NSString *outputFile = [self swiftFileForClassObject:base];
        NSString *filename = [NSString stringWithFormat:@"%@.swift", base.className];

        [outputFile writeToURL:[url URLByAppendingPathComponent:filename]
                    atomically:YES
                      encoding:NSUTF8StringEncoding
                         error:&fileError];

        if (fileError) {
            filesHaveHadError = YES;
        } else {
            filesHaveBeenWritten = YES;
        }
    }

    if (generatedErrorFlag) { *generatedErrorFlag = filesHaveHadError; }
    return filesHaveBeenWritten;
}

- (NSDictionary *)getOutputFilesForClassObject:(ClassBaseObject *)classObject {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    dict[[NSString stringWithFormat:@"%@.swift", classObject.className]] = [self swiftFileForClassObject:classObject];
    return [NSDictionary dictionaryWithDictionary:dict];
}

- (NSSet *)reservedWords {
    return [NSSet setWithObjects:
            @"associatedtype", @"class", @"deinit", @"enum", @"extension", @"fileprivate",
            @"func", @"import", @"init", @"inout", @"internal", @"let", @"open",
            @"operator", @"private", @"protocol", @"public", @"rethrows", @"static",
            @"struct", @"subscript", @"typealias", @"var", @"break", @"case", @"continue",
            @"default", @"defer", @"do", @"else", @"fallthrough", @"for", @"guard",
            @"if", @"in", @"repeat", @"return", @"switch", @"where", @"while", @"as",
            @"Any", @"catch", @"false", @"is", @"nil", @"self", @"super", @"throw",
            @"throws", @"true", @"try", @"_", nil];
}

- (NSString *)classNameForObject:(ClassBaseObject *)classObject fromReservedWord:(NSString *)reservedWord {
    // Capitalize and append Model to avoid conflicts and invalid identifiers
    NSString *capitalized = [reservedWord capitalizedString];
    return [capitalized stringByAppendingString:@"Model"];
}

- (NSString *)propertyNameForObject:(ClassPropertiesObject *)propertyObject inClass:(ClassBaseObject *)classObject fromReservedWord:(NSString *)reservedWord {
    // Lower camel case and append Property to avoid conflicts
    NSString *first = [[reservedWord substringToIndex:1] lowercaseString];
    NSString *rest = reservedWord.length > 1 ? [reservedWord substringFromIndex:1] : @"";
    return [[first stringByAppendingString:rest] stringByAppendingString:@"Property"];
}

- (NSString *)swiftFileForClassObject:(ClassBaseObject *)classObject {
    NSMutableString *file = [NSMutableString string];
    [file appendString:@"// Generated by JSONAccelerator\n"]; 
    [file appendString:@"import Foundation\n\n"]; 
    [file appendFormat:@"public struct %@: Codable {\n", classObject.className];

    // Properties
    for (ClassPropertiesObject *property in (classObject.properties).allValues) {
        NSString *typeString = [self swiftTypeForProperty:property];
        // default optional to be tolerant of missing keys
        [file appendFormat:@"    public var %@: %@?\n", property.jsonName, typeString];
    }

    // CodingKeys
    [file appendString:@"\n    private enum CodingKeys: String, CodingKey {\n"]; 
    for (ClassPropertiesObject *property in (classObject.properties).allValues) {
        [file appendFormat:@"        case %@ = \"%@\"\n", property.jsonName, property.jsonName];
    }
    [file appendString:@"    }\n"]; 

    // init(from:) is synthesized; provide convenience init(dict:)
    [file appendString:@"\n    public init() {}\n"]; 
    [file appendString:@"\n    public init(dictionary: [String: Any]) {\n"]; 
    for (ClassPropertiesObject *property in (classObject.properties).allValues) {
        NSString *typeString = [self swiftTypeForProperty:property];
        // best-effort value extraction
        [file appendFormat:@"        self.%@ = dictionary[\"%@\"] as? %@\n", property.jsonName, property.jsonName, typeString];
    }
    [file appendString:@"    }\n"]; 

    // toDictionary
    [file appendString:@"\n    public func toDictionary() -> [String: Any] {\n"]; 
    [file appendString:@"        var dict: [String: Any] = [:]\n"]; 
    for (ClassPropertiesObject *property in (classObject.properties).allValues) {
        [file appendFormat:@"        if let value = %@ { dict[\"%@\"] = value }\n", property.jsonName, property.jsonName];
    }
    [file appendString:@"        return dict\n    }\n"]; 

    [file appendString:@"}\n"]; 
    return file;
}

- (NSString *)swiftTypeForProperty:(ClassPropertiesObject *)property {
    switch (property.type) {
        case PropertyTypeString:
            return @"String";
        case PropertyTypeArray: {
            // Array of class or primitive
            switch (property.collectionType) {
                case PropertyTypeString:
                    return @"[String]";
                case PropertyTypeInt:
                    return @"[Int]";
                case PropertyTypeDouble:
                    return @"[Double]";
                case PropertyTypeBool:
                    return @"[Bool]";
                case PropertyTypeClass:
                    return [NSString stringWithFormat:@"[%@]", property.referenceClass.className];
                default:
                    return @"[Any]";
            }
        }
        case PropertyTypeDictionary:
            return @"[String: Any]";
        case PropertyTypeInt:
            return @"Int";
        case PropertyTypeBool:
            return @"Bool";
        case PropertyTypeDouble:
            return @"Double";
        case PropertyTypeClass:
            return property.referenceClass.className ?: @"Any";
        case PropertyTypeOther:
        default:
            return @"Any";
    }
}

@end


